<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>2048游戏</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }

        #box {
            background: #bbada0;
            margin: 36px auto;
            width: 500px;
            height: 500px;
            border-radius: 8px;
            position: relative;
        }

        .grid-cell {
            position: absolute;
            width: 100px;
            height: 100px;
            line-height: 100px;
            background: #ccc0b3;
            color: #ffffff;
            text-align: center;
            font-size: 36px;
            border-radius: 8px;
            transition: all 0.2s linear;
        }

        .tc {
            text-align: center;
        }

        .f18 {
            font-size: 18px;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <div class="tc f18">通过WASD键或方向键进行控制</div>
    <script type="text/javascript">
        // 2048是一个小游戏，简单易上手，真的很好玩的

        var grids = []
        var win = window
        var doc = document


        win.onload = function () {
            // 初始化宫格
            initGrid()
            // 随机出两个格子
            setRandomGrid()
            setRandomGrid()
            // 键盘事件监听
            doc.onkeydown = event => {
                const directions = {
                    '65': n => moveLeft(n),
                    '68': n => moveRight(n),
                    '87': n => moveTop(n),
                    '83': n => moveBottom(n),
                    '37': n => moveLeft(n),
                    '39': n => moveRight(n),
                    '38': n => moveTop(n),
                    '40': n => moveBottom(n),
                }

                const moveFn = directions[parseInt(event.keyCode)]

                if (moveFn) {
                    // 移动前的数据，用来对比是否移动了数据
                    var pregrids = JSON.parse(JSON.stringify(grids))

                    for (let i = 0; i < 4; i++) {
                        moveFn(i)
                    }

                    // 根据数字给宫格填充颜色
                    setGrid()

                    // 如果进行移动了，随机生出一个宫格
                    hasMove(pregrids) && setRandomGrid()
                }
            }
        }

        // 根据数字获取对应的颜色
        function getColor(num) {
            // 对应格子的颜色
            var colorObj = {
                '2': '#eee3da',
                '4': '#ede0c8',
                '8': '#f2b179',
                '16': '#f59563',
                '32': '#f67c5f',
                '64': '#f65e3b',
                '128': '#edcf72',
                '256': '#edcc61',
                '512': '#9c0',
                '1024': '#33b5e5',
                '2048': '#09c',
                '4096': '#a6c',
                '8192': '#93c'
            }
            return colorObj[num] || '#ccc0b3';
        }

        // 通过对象生成样式字符串
        function getStyleStr(obj) {
            return Object.keys(obj)
                .map(k => k + ':' + obj[k] + ';')
                .join('')
        }

        // 初始化宫格
        function initGrid() {
            var sHtml = ''
            for (var i = 0; i < 4; i++) {
                grids[i] = []
                for (var j = 0; j < 4; j++) {
                    grids[i][j] = 0
                    let styleStr = getStyleStr({
                        left: 20 + j * 120 + 'px',
                        top: 20 + i * 120 + 'px'
                    })

                    sHtml += '<div class="grid-cell" style="' + styleStr + '"></div>'
                }
            }

            doc.getElementById('box').innerHTML = sHtml
        }

        // 判断是否进行了移动
        function hasMove(pregrids) {
            return JSON.stringify(pregrids) !== JSON.stringify(grids)
        }

        // 根据数字给宫格填充颜色
        function setGrid() {
            var list = doc.querySelectorAll('.grid-cell')
            for (var i = 0; i < 4; i++) {
                for (var j = 0; j < 4; j++) {
                    var el = list[i * 4 + j]
                    var num = grids[i][j]
                    el.innerHTML = num || ''
                    el.style.background = getColor(num)
                }
            }
        }

        // 获取所有空格
        function getEmpty() {
            let res = []
            for (let i = 0; i < 4; i++) {
                for (let j = 0; j < 4; j++) {
                    if (grids[i][j] === 0) {
                        res.push(4 * i + j)
                    }
                }
            }
            return res
        }

        // 获取一个随机数
        function getRandom(num) {
            return Math.floor(Math.random() * num)
        }

        // 随机生出一个格子
        function setRandomGrid() {
            // 获取所有空的宫格
            let emptys = getEmpty()
            // 从中选一个生成新宫格
            var n = emptys[getRandom(emptys.length)]
            var x = Math.floor(n / 4)
            var y = n % 4

            var el = doc.querySelectorAll('.grid-cell')[n]
            // 随机生成一个2或4
            var num = [2, 4][getRandom(2)]
            grids[x][y] = num
            el.innerHTML = num
            el.style.background = getColor(num);
            isAllHas() && checkIsOver()
        }

        // 判断所有宫格是否都有数字
        function isAllHas() {
            for (var i = 0; i < 4; i++) {
                for (var j = 0; j < 4; j++) {
                    if (grids[i][j] === 0) {
                        return false
                    }
                }
            }
            return true
        }

        // 判断游戏是否结束
        function checkIsOver() {
            // 不能在两个方向移动则代表游戏结束
            if (!canMoveLeft() && !canMoveUp()) {
                doc.onkeydown = null
                alert('GAME OVER')
            }
        }

        // 可以在水平方向移动
        function canMoveHorizontal() {
            for (var i = 0; i < 4; i++) {
                var pre = null
                for (var j = 0; j < 4; j++) {
                    let item = grids[i][j]
                    if (item === pre) {
                        return true
                    } else {
                        pre = item
                    }
                }
            }
            return false
        }

        // 可以在垂直方向移动
        function canMoveVertical() {
            for (var i = 0; i < 4; i++) {
                var pre = null
                for (var j = 0; j < 4; j++) {
                    let item = grids[j][i]
                    if (item === pre) {
                        return true
                    } else {
                        pre = item
                    }
                }
            }
            return false
        }

        function moveLeft(n) {
            const temp = getArr(grids[n])
            const len = temp.length
            for (var i = 0; i < len; i++) {
                grids[n][i] = temp[i]
            }
            for (var i = len; i < 4; i++) {
                grids[n][i] = 0
            }
        }

        function moveRight(n) {
            const temp = getArr(grids[n].reverse()).reverse()
            const len = temp.length
            for (var i = 0; i < 4 - len; i++) {
                grids[n][i] = 0
            }
            for (var i = 0; i < len; i++) {
                grids[n][4 - len + i] = temp[i]
            }
        }

        function moveTop(n) {
            const temp = getArr(grids.map(i => i[n]))
            const len = temp.length
            for (var i = 0; i < len; i++) {
                grids[i][n] = temp[i]
            }
            for (var i = len; i < 4; i++) {
                grids[i][n] = 0
            }
        }

        function moveBottom(n) {
            const temp = getArr(grids.map(i => i[n]).reverse()).reverse()
            const len = temp.length
            for (var i = 0; i < 4 - len; i++) {
                grids[i][n] = 0
            }
            for (var i = 0; i < len; i++) {
                grids[4 - len + i][n] = temp[i]
            }
        }

        // 对数据进行处理，合并相邻的相同数字
        function getArr(arr) {
            var pre = null
            var temp = []
            for (var i = 0; i < 4; i++) {
                if (arr[i] > 0) {
                    if (arr[i] === pre) {
                        temp[temp.length - 1] = 2 * arr[i]
                        pre = null
                    } else {
                        pre = arr[i]
                        temp.push(arr[i])
                    }
                }
            }
            return temp
        }
    </script>
</body>

</html>